﻿using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Threading;
using Point = System.Drawing.Point;

using Acap = Autodesk.AutoCAD.ApplicationServices.Application;
using JoinBox.JoinBox_Code.WPF.多文档标签.ViewModel;
using JoinBoxCurrency;

using static Win32API.WinApi;

namespace JoinBox.JoinBox_Code.WPF.多文档标签.View
{
    public class Anchor : IDisposable
    {
        // cad主窗口句柄
        private IntPtr _Handle_AC;
        // cad文档管理器窗口句柄
        private IntPtr _Handle_Mdi;
        // 嵌入的WPF窗体容器
        public EmbedWPF EmbedDoWPF = null;
        /// <summary>
        /// 暂停刷新,用来移除多文档栏
        /// </summary>
        bool _refreshPosSuspend = false;
        /// <summary>
        /// 文档栏会比挤出更快进入程序
        /// 所以需要这个标记判断挤出成功,否则计算文档高度不正确 DocSizeTool.TiaoZheng()
        /// </summary>
        static bool _SetTopOK = false;


        //子类化cad
        private CallProc_Acad _AcadCallWProc;
        private CallProc_Mdi _AcadMdiCallWProc;

        private DocWindow DocWindow;
        //键盘钩子
        Win32API.KeyboardHook _keyboardHook;
        Thread WPF_Render_Thread;
        /// <summary>
        /// 锚固窗体
        /// </summary>
        public Anchor(AcadIntermediary AcadIntermediary)
        {
            _refreshPosSuspend = false;

            //Acad主窗口的句柄
            _Handle_AC = Acap.MainWindow.Handle;
            if (_Handle_AC == IntPtr.Zero)
            {
                return;
            }
            // 拦截cad消息机制,将控制代码指派到这个视窗,同时挤出工具条的空间,令其他空间缩小
            _AcadCallWProc = new CallProc_Acad(this);
            _AcadCallWProc.AssignHandle(_Handle_AC);

            //活动文档父窗用于存放多文档栏
            var doc = Acap.DocumentManager.MdiActiveDocument;
            _Handle_Mdi = GetParent(doc.Window.Handle);
            _AcadMdiCallWProc = new CallProc_Mdi(this);
            _AcadMdiCallWProc.AssignHandle(_Handle_Mdi);


            try
            {
                //不允许赋值两次
                Thread.CurrentThread.Name = "Acad_Thread";
            }
            catch
            { }

            uint number = 0;
            // 多文档标签的控件
            WPF_Render_Thread = new Thread(() =>
            {
                Thread.CurrentThread.Name = $"WPF_Thread{++number}";
                while (true)
                {
                    //等到cad界面完成,再进行嵌入
                    if (Acap.IsQuiescent && _SetTopOK)
                    {
                        //设置一次初始化值先,再加入容器
#if true
                        EmbedDoWPF = new EmbedWPF("JJBoxEmbedWPF", _Handle_AC);
#else
                        //福萝卜用的是这样的,但是我用了之后发现有刷新问题..
                        EmbedDoWPF = new EmbedWPF("JJBoxEmbedWPF", _Handle_Mdi);
#endif
                        SetSizeWPF();
                        DocWindow = new DocWindow(AcadIntermediary);

                        //CAD界面没有完成会导致下面两句卡死
                        EmbedDoWPF.Add(DocWindow);//HwndSource卡死
                        RefreshPos();                          //moveWindow卡死
                        DragDrop(EmbedDoWPF._DocWindow);
                        Dispatcher.Run();//循环中处理事件队列(其后语句会在WPF关闭后执行)
                        break;
                    }
                    else
                    {
                        Thread.Sleep(200);
                    }
                }
            });
            WPF_Render_Thread.SetApartmentState(ApartmentState.STA);//WPF需要这样的单线程
            WPF_Render_Thread.Start();

            _keyboardHook = new Win32API.KeyboardHook();
            _keyboardHook.KeyDownEvent += KeyboardHook_KeyDownEvent;

#if false
            AutoLoadRun();
            Acap.DocumentManager.DocumentCreated += this.AutoLoadRun;
            //反应器->销毁前返回文档(先)
            Acap.DocumentManager.DocumentToBeDestroyed += DocumentManager_DocumentToBeDestroyed; 
#endif
        }


        private void KeyboardHook_KeyDownEvent(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.N && Control.ModifierKeys == Keys.Control)
            {
                //全局钩子
                if (GetForegroundWindow() == _Handle_AC)
                {
                    //判断当前焦点
                    if (DocWindow != null)
                    {
                        DocWindow.Qnew();
                        ((Win32API.KeyboardHook)sender).IsHookBreak = true;
                    }
                }
            }
        }

        /// <summary>
        /// 拖放操作(这里可能有跨线程操作的问题)
        /// </summary>
        /// <param name="control"></param>
        void DragDrop(System.Windows.Controls.Control control)
        {
            //设置可拖拽拖入dll安装控件
            if (control == null)
            {
                return;
            }
            new ControlDragDrop(control, (CDD_sender, CDD_e) =>
            {
                try
                {
                    string path = ((Array)CDD_e.Data.GetData(System.Windows.DataFormats.FileDrop)).GetValue(0).ToString();
                    string extension = System.IO.Path.GetExtension(path).ToLower();//扩展名
                    if (extension == ".dll")
                    {
                        Editor.WriteMessage("**执行加载dll操作" + Environment.NewLine);

                        var ad = new AssemblyDependent(path);
                        var msg = ad.Load();

                        bool allyes = true;
                        foreach (var item in msg)
                        {
                            if (!item.LoadYes)
                            {
                                Editor.WriteMessage("**" + item.Path +
                                    Environment.NewLine + "**此文件已加载过,重复名称,重复版本号,本次不加载!" + Environment.NewLine);
                                allyes = false;
                            }
                        }
                        if (allyes)
                        {
                            Editor.WriteMessage("**链式加载成功!" + Environment.NewLine);
                        }
                    }
                    else if (extension == ".dwg")
                    {
                        //前台打开图纸
                        //OpreateCad.Opendwg(path);
                    }
                    else
                    {
                        Editor.WriteMessage("**拖拽dll才能支持加载" + Environment.NewLine);
                        return;
                    }
                }
                catch (Exception ee)
                {
                    Editor.WriteMessage("**加载出现错误::" + Environment.NewLine + ee.Message + Environment.NewLine);
                }
            });
        }

        /// <summary>
        /// 设置WPF大小
        /// </summary>
        void SetSizeWPF()
        {
            if (EmbedDoWPF == null)
            {
                return;
            }

            //返回客户区大小
            GetClientRect(_Handle_AC, out RECT lpRect);

            //客户区左上角转为屏幕坐标
            var lpPoint = new Point(lpRect.Left, lpRect.Top);
            ClientToScreen(_Handle_AC, ref lpPoint);

            //返回cad文档管理器窗口边框尺寸 
            GetWindowRect(_Handle_Mdi, out RECT rect2);

            //窗口大小 
            int rect2_Width = rect2.Right - rect2.Left;

            var x = rect2.Left - lpPoint.X;
            var y = rect2.Top - lpPoint.Y;

            //左上角点开始,此处只作为初始化值,并没有移动的功能.
            EmbedDoWPF.Parameters.SetPosition(x, y);

            //设置大小(无法实时刷新宽度,所以第一次就弄个屏幕长,然后会被cad主窗口截断不需要的部分) 
            // _EmbedWPF.Parameters.SetSize(rect2_Width + 1000, rect2_Height);

            //获得当前屏幕的大小 
            var allScreensRect = Screen.AllScreens[0].WorkingArea;
            EmbedDoWPF.Parameters.SetSize(allScreensRect.Width, DocumentTab.HeightDPI);

            if (EmbedDoWPF.Handle != IntPtr.Zero)
            {
                //用win32的方法实现移动, 这里高度增加,会令整体高度拉高,而控件在中间
#if true
                var rec = new RECT(x, y, x + rect2_Width, y + DocumentTab.HeightDPI);
#else
                var rec = new RECT(x, 0, x + rect2_Width, WPF_Height);
#endif
                MoveWindow(EmbedDoWPF.Handle, rec);
            }
        }

        /// <summary>
        /// 刷新位置
        /// </summary>
        public void RefreshPos()
        {
            if (_refreshPosSuspend)
            {
                return;
            }
            SetSizeWPF();
            EmbedDoWPF?.SetPos();
        }

        //窗口控件子类化,消息拦截
        public class CallProc_Acad : NativeWindow
        {
            private readonly Anchor anchor;

            /// <summary>
            /// 拦截消息:CAD主窗口
            /// </summary>
            /// <param name="Anchor"></param>
            public CallProc_Acad(Anchor Anchor)
            {
                anchor = Anchor;
            }

            //57600_new || 18_qnew 对应cad命令
            IntPtr _new = (IntPtr)57600;
            IntPtr _qnew = (IntPtr)18;
            IntPtr _chongZhi = (IntPtr)0x0001e100;//重置之后ctrl+n取得的
            /// <summary>
            /// 控制是否回调
            /// </summary>
            bool IsCallback = true;

            //<子类化-处理cad的新建图纸错误> 
            protected override void WndProc(ref Message m)
            {
                try
                {
                    var msg = (WM)m.Msg;
                    IsCallback = true;

#if true3
                    //鼠标按着文档窗口移动时候触发 WM.WM_MOUSEACTIVATE
                    //https://social.msdn.microsoft.com/Forums/en-US/572cf1ac-c55f-47cb-b41c-bbc0d7fd23b8/wndproc-detect-title-bar-clicked

                    //这里是可用的,但是拖动文档窗口会频繁发生,拦截每个文档栏的拖动又会产生消息通知滞后,所以不想做了
                    //拖动窗口/最大化/还原等尺寸改变时候触发,触发比WM_SIZE多
                    //调整cad文档尺寸 
                    //这里只是拦截cad主窗口改变大小
                    if (msg == WM.WM_WINDOWPOSCHANGED)
                    {
                        DocSizeTool.TiaoZheng();
                    }
#endif


                    //移动cad界面时候触发
                    if (msg == WM.WM_MOVE)
                    {
                        anchor.RefreshPos();
                    }
                    else if (msg == WM.WM_COMMAND)
                    {
                        //这里拦截一下两种发送方式
                        //WinApi.SendMessage(Acap.MainWindow.Handle, (int)MsgType.WM_COMMAND, (IntPtr)18, IntPtr.Zero);//qnew 
                        //WinApi.SendMessage(Acap.MainWindow.Handle, (int)MsgType.WM_COMMAND, (IntPtr)57600, IntPtr.Zero);//new
                        if (m.WParam == _new || m.WParam == _qnew || m.WParam == _chongZhi)
                        {
                            // 之后是阻止cad接收win32的ctrl+n消息,需要键盘钩子hook
                            // 从而防止发送消息引起的致命错误
                            anchor.DocWindow.Qnew();
                            IsCallback = false;
                        }
                    }

                    if (IsCallback)
                    {
                        base.WndProc(ref m);//回调函数  
                    }
                }
                catch //cad崩溃的时候会触发
                { }
            }
        }


        //窗口控件子类化,消息拦截
        public class CallProc_Mdi : NativeWindow
        {
            private readonly Anchor anchor;

            /// <summary>
            /// 拦截消息:MDI窗口
            /// </summary>
            /// <param name="Anchor"></param>
            public CallProc_Mdi(Anchor Anchor)
            {
                anchor = Anchor;
            }

            //根据win的消息机制,来实现对mdi窗口的控制
            protected override void WndProc(ref Message m)
            {
                try
                {
                    var msg = (WM)m.Msg;

                    //if (!(m.Msg == 553 ||
                    //    msg == WM.WM_SETCURSOR ||
                    //    msg == WM.WM_NCACTIVATE
                    //    ))
                    //{
                    //    Debug.WriteLine(msg);
                    //}

                    //if (msg == WM.WM_MOUSEACTIVATE)//按着文档窗口时候触发
                    //{
                    //    if (m.WParam != IntPtr.Zero)
                    //    {                        
                    //        var str = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS));

                    //    } 
                    //}                  

                    if (msg == WM.WM_SIZE || msg == WM.WM_MOVE)
                    {
                        anchor.RefreshPos();
                    }
                    else if (msg == WM.WM_NCCALCSIZE)//0x83当某个窗口的客户区域必须被核算时发送此消息
                    {
                        if (m.WParam != IntPtr.Zero)
                        {
                            //nc消息要算窗口大小,预留一个空间出来放窗口,它可以缩小其他窗口的空间,而不是把WPF遮蔽到上面去
                            //非托管内存块->托管对象
                            var str = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS));
                            //预留高度,如果不-2会超出来一点点
                            str.rgrc[0].Top = str.rgrc[0].Top + Convert.ToInt32(DocumentTab.HeightDPI - 2);
                            //托管对象->非托管内存块
                            Marshal.StructureToPtr(str, m.LParam, true);
                            _SetTopOK = true;
                        }
                    }
                    base.WndProc(ref m);//回调函数
                }
                catch //cad崩溃的时候会触发
                { }
            }
        }




        #region Dispose
        public bool Disposed = false;

        /// <summary>
        /// 显式调用Dispose方法,继承IDisposable
        /// </summary>
        public void Dispose()
        {
            //由手动释放
            Dispose(true);
            //通知垃圾回收机制不再调用终结器(析构器)_跑了这里就不会跑析构函数了
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// 析构函数,以备忘记了显式调用Dispose方法
        /// </summary> 
        ~Anchor()
        {
            //由系统释放
            Dispose(false);
        }

        //https://kb.cnblogs.com/page/97871/
        /// <summary>
        /// 释放
        /// </summary>
        /// <param name="ing"></param>
        protected virtual void Dispose(bool ing)
        {
            if (Disposed)
            {
                //不重复释放
                return;
            }
            //让类型知道自己已经被释放
            Disposed = true;

            //停止刷新,移除事件,关闭虚窗
            _refreshPosSuspend = true;

            //卸载钩子
            _keyboardHook?.Dispose();
            //卸载界面
            EmbedDoWPF?.Dispose();

            //释放句柄
            _AcadMdiCallWProc?.ReleaseHandle();
            _AcadCallWProc?.ReleaseHandle();

            //排序窗口,触发刷新
            SetWindowPos(_Handle_Mdi);
        }
        #endregion



    }


    public static class Editor
    {
        public static void WriteMessage(object str)
        {
#if !WpfApp1
            var ed = Acap.DocumentManager.MdiActiveDocument.Editor;
            ed.WriteMessage(Environment.NewLine + str.ToString() + Environment.NewLine);
#else
            Debug.WriteLine(str);
#endif
        }
    }
}